﻿#region Copyright Notice

//Copyright © 2007-2011, PARROT SA, all rights reserved. 

//DISCLAIMER 
//The APIs is provided by PARROT and contributors "AS IS" and any express or implied warranties, including, but not limited to, the implied warranties of merchantability 
//and fitness for a particular purpose are disclaimed. In no event shall PARROT and contributors be liable for any direct, indirect, incidental, special, exemplary, or 
//consequential damages (including, but not limited to, procurement of substitute goods or services; loss of use, data, or profits; or business interruption) however 
//caused and on any theory of liability, whether in contract, strict liability, or tort (including negligence or otherwise) arising in any way out of the use of this 
//software, even if advised of the possibility of such damage. 

//Author            : Wilke Jansoone
//Email             : wilke.jansoone@digitude.net
//Publishing date   : 28/11/2010 

//Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions
//are met:
//    - Redistributions of source code must retain the above copyright notice, this list of conditions, the disclaimer and the original author of the source code.
//    - Neither the name of the PixVillage Team, nor the names of its contributors may be used to endorse or promote products derived from this software without 
//      specific prior written permission.

#endregion

#region Imports

using System;
using System.Timers;

using SlimDX.DirectInput;
using SlimDX;

using Wilke.Interactive.Drone.Contracts;
using Wilke.Interactive.Drone.Control;

#endregion

namespace Wilke.Interactive.Drone.InputProviders
{
    /// <summary>
    /// This class captures input coming from an analog joystick.
    /// </summary>
    public class JoystickInputProvider : IInputProvider
    {
        #region Private Fields

        private Joystick joystick;

        private JoystickState currentState = new JoystickState();

        private int previousX;
        private int previousY;
        private int previousRotationZ;
        private int previousSlider;

        private Single progressivePitch;
        private Single progressiveRoll;
        private Single progressiveGaz;
        private Single progressiveYaw;

        private Single picthThrottleValue;
        private Single rollThrottleValue;
        private Single heightThrottleValue;
        private Single yawThrottleValue;

        #endregion

        #region Public Properties

        /// <summary>
        /// Gets or sets the joy stick range (the maximum value a joystick produces for a specific movement).
        /// </summary>
        /// <value>The joy stick range.</value>
        public int JoyStickRange { get; set; }

        #endregion

        #region Private Properties

        private Timer InputTimer { get; set; }

        #endregion

        #region Construction

        /// <summary>
        /// Initializes a new instance of the <see cref="JoystickInputProvider"/> class.
        /// </summary>
        /// <param name="droneCommander">The drone commander.</param>
        /// <param name="joyStickRange">The joy stick range.</param>
        public JoystickInputProvider(IDroneCommander droneCommander, int joyStickRange)
        {
            this.JoyStickRange = joyStickRange;
            this.DroneCommander = droneCommander;
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="JoystickInputProvider"/> class.
        /// </summary>
        /// <param name="droneCommander">The drone commander.</param>
        public JoystickInputProvider(IDroneCommander droneCommander)
            : this(droneCommander, Constants.DefaultJoyStickRange)
        { }

        #endregion

        #region Event Handlers

        /// <summary>
        /// Handles the Elapsed event of the InputTimer control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.Timers.ElapsedEventArgs"/> instance containing the event data.</param>
        void InputTimer_Elapsed(object sender, ElapsedEventArgs e)
        {
            if (joystick.Acquire().IsFailure)
                return;

            if (joystick.Poll().IsFailure)
                return;

            currentState = joystick.GetCurrentState();
            if (Result.Last.IsFailure)
                return;

            if (currentState.Y != previousY)
            {
                progressivePitch = (float)currentState.Y * picthThrottleValue / this.JoyStickRange;
            }

            if (currentState.X != previousX)
            {
                progressiveRoll = (float)currentState.X * rollThrottleValue / this.JoyStickRange; 
            }

            if (currentState.RotationZ != previousRotationZ)
            {
                progressiveYaw = (float)currentState.RotationZ * yawThrottleValue / this.JoyStickRange;
            }

            int[] slider = currentState.GetSliders();

            if (slider[0] != previousSlider)
            {
                progressiveGaz = (float)slider[0] * heightThrottleValue / this.JoyStickRange;
            }

            bool[] buttons = currentState.GetButtons();

            if (buttons[0])
            {
                DroneCommander.StopEngines();
            }

            if (buttons[1])
            {
                DroneCommander.StartEngines();
            }

            if (buttons[2])
            {
                DroneCommander.SetFlatTrim();
            }

            if (buttons[3])
            {
                DroneCommander.SetFlatTrim();
            }

            if (buttons[6])
            {
                DroneCommander.StartReset();
            }

            if (buttons[7])
            {
                DroneCommander.StopReset();
            }

            DroneCommander.SetFlightParameters(progressiveRoll, progressivePitch, - progressiveGaz, progressiveYaw);

            previousX = currentState.X;
            previousY = currentState.Y;
            previousRotationZ = currentState.RotationZ;
            previousSlider = slider[0];
        }

        #endregion

        #region IInputProvider Members

        /// <summary>
        /// Gets or sets the instance that implements the <see cref="IDroneCommander">IDroneCommander</see> interface.
        /// </summary>
        /// <value>
        /// The object that implements the <see cref="IDroneCommander">IDroneCommander</see> interface.
        /// </value>
        public IDroneCommander DroneCommander { get; set; }

        /// <summary>
        /// Initializes the specified input provider.
        /// </summary>
        /// <returns>
        /// Returns <c>true</c> if the provider was initialized correclty; otherwise, <c>false</c>.
        /// </returns>
        /// <remarks>The throttling values are implicitly set to 1, so the values registered by the inputprovider will be passed unchanged to the object that implements the <see cref="IDroneCommander">IDroneCommander</see> interface.</remarks>
        public bool Initialize()
        {
            return Initialize(1, 1, 1, 1);
        }

        /// <summary>
        /// Initializes the specified input provider.
        /// </summary>
        /// <param name="rollThrottle">The roll throttle value between 0 and 1. The actual throttle value registered by the inputprovider will be multiplied by this value.</param>
        /// <param name="pitchThrottle">The pitch throttle value between 0 and 1. The actual pitch value registered by the inputprovider will be multiplied by this value.</param>
        /// <param name="heightThrottle">The height throttle value between 0 and 1. The actual height value registered by the inputprovider will be multiplied by this value.</param>
        /// <param name="yawThrottle">The yaw throttle value between 0 and 1. The actual yaw value registered by the inputprovider will be multiplied by this value.</param>
        /// <returns>Returns <c>true</c> if the provider was initialized correclty; otherwise, <c>false</c>.</returns>
        /// <remarks>The throttling is applied in order to be able to control the ARDrone in smaller places. A throttle value of 1 means that there is no throttling, a value of 0 means that the resulting value will also be 0.</remarks>
        public bool Initialize(float rollThrottle, float pitchThrottle, float heightThrottle, float yawThrottle)
        {
            try
            {
                this.picthThrottleValue = pitchThrottle;
                this.rollThrottleValue = rollThrottle;
                this.heightThrottleValue = heightThrottle;
                this.yawThrottleValue = yawThrottle;

                // make sure that DirectInput has been initialized
                DirectInput dinput = new DirectInput();

                // search for devices
                foreach (DeviceInstance device in dinput.GetDevices(DeviceClass.GameController, DeviceEnumerationFlags.AttachedOnly))
                {
                    joystick = new Joystick(dinput, device.InstanceGuid);
                    break;
                }

                if (joystick != null)
                {
                    foreach (DeviceObjectInstance deviceObject in joystick.GetObjects())
                    {
                        if ((deviceObject.ObjectType & ObjectDeviceType.Axis) != 0)
                            joystick.GetObjectPropertiesById((int)deviceObject.ObjectType).SetRange(-1000, 1000);
                    }

                    // acquire the device
                    joystick.Acquire();

                    // set the timer to go off 12 times a second to read input
                    // NOTE: Normally applications would read this much faster.
                    // This rate is for demonstration purposes only.
                    InputTimer = new Timer(1000 / 12);
                    InputTimer.Elapsed += new ElapsedEventHandler(InputTimer_Elapsed);
                    InputTimer.Start();
                }
                else
                {
                    throw new Exception("There are no joysticks attached to the system.");
                }

                return true;
            }
            catch (Exception)
            {
                return false;
            }
        }

        #endregion

        #region IDisposable Members

        /// <summary>
        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
        /// </summary>
        public void Dispose()
        {
            if(InputTimer != null)
            {
                InputTimer.Stop();
            }

            if (joystick != null)
            {
                joystick.Unacquire();
                joystick.Dispose();
            }

            joystick = null;
        }

        #endregion
    }
}
